home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Best of Shareware
/
Best of PC Windows Shareware 1.0 - Wayzata Technology (7111) (1993).iso
/
mac
/
ZIPPED
/
DOS
/
GRAPHICS
/
RAYSH386.ZIP
/
SRC
/
BLOB.C
< prev
next >
Wrap
C/C++ Source or Header
|
1992-04-28
|
19KB
|
711 lines
/*
* blob.c
*
* Copyright (C) 1990, 1991, Mark Podlipec, Craig E. Kolb
* All rights reserved.
*
* This software may be freely copied, modified, and redistributed
* provided that this copyright notice is preserved on all copies.
*
* You may not distribute this software, in whole or in part, as part of
* any commercial product without the express consent of the authors.
*
* There is no warranty or other guarantee of fitness of this software
* for any purpose. It is provided solely "as is".
*
* $Id: blob.c,v 4.0 91/07/17 14:36:07 kolb Exp Locker: kolb $
*
* $Log: blob.c,v $
* Revision 4.0 91/07/17 14:36:07 kolb
* Initial version.
*
*/
#include "geom.h"
#include "blob.h"
static Methods *iBlobMethods = NULL;
static char blobName[] = "blob";
unsigned long BlobTests, BlobHits;
/*
* Blob/Metaball Description
*
* In this implementation a Blob is made up of a threshold T, and a
* group of 1 or more Metaballs. Each metaball 'i' is defined by
* its position (xi,yi,zi), its radius ri, and its strength si.
* Around each Metaball is a density function di(Ri) defined by:
*
* di(Ri) = c4i * Ri^4 + c2i * Ri^2 + c0i 0 <= Ri <= ri
* di(Ri) = 0 ri < Ri
*
* where
* c4i = si / (ri^4)
* c2i = -(2 * si) / (ri^2)
* c0i = si
* Ri^2 is the distance from a point (x,y,z) to the center of
* the Metaball - (xi,yi,zi).
*
* The density function looks sort of like a W (Float-u) with the
* center hump crossing the d-axis at si and the two humps on the
* side being tangent to the R-axis at -ri and +ri. Only the
* range [0,ri] is being used.
* I chose this function so that derivative = 0 at the points 0 and +ri.
* This keeps the surface smooth when the densities are added. I also
* wanted no Ri^3 and Ri terms, since the equations are easier to
* solve without them. This led to the symmetry about the d-axis and
* the derivative being equal to zero at -ri as well.
*
* The surface of the Blob is defined by:
*
*
* N
* ---
* F(x,y,z) = \ di(Ri) = T
* /
* ---
* i=0
*
* where
*
* di(Ri) is given above
* Ri^2 = (x-xi)^2 + (y-yi)^2 + (z-zi)^2
* N is number of Metaballs in Blob
* T is the threshold
* (xi,yi,zi) is the center of Metaball i
*
*/
/*****************************************************************************
* Create & return reference to a metaball.
*/
Blob *
BlobCreate(T, mlist, npoints)
Float T;
MetaList *mlist;
int npoints;
{
Blob *blob;
int i;
MetaList *cur;
/*
* There has to be at least one metaball in the blob.
* Note: if there's only one metaball, the blob
* will be a sphere.
*/
if (npoints < 1)
{
RLerror(RL_WARN, "blob field not correct.\n");
return (Blob *)NULL;
}
/*
* Initialize primitive and Geom structures
*/
blob = (Blob *)RayMalloc(sizeof(Blob));
blob->T = T;
blob->list=(MetaVector *)
RayMalloc( (unsigned)(npoints*sizeof(MetaVector)) );
blob->num = npoints;
/*
* Initialize Metaball list
*/
for(i=0;i<npoints;i++)
{
cur = mlist;
if ( (cur->mvec.c0 < EPSILON) || (cur->mvec.rs < EPSILON) )
{
RLerror(RL_WARN,"Degenerate metaball in blob (sr).\n");
return (Blob *)NULL;
}
/* store radius squared */
blob->list[i].rs = cur->mvec.rs * cur->mvec.rs;
/* Calculate and store coefficients for each metaball */
blob->list[i].c0 = cur->mvec.c0;
blob->list[i].c2 = -(2.0 * cur->mvec.c0) / blob->list[i].rs;
blob->list[i].c4 = cur->mvec.c0 /
(blob->list[i].rs * blob->list[i].rs);
blob->list[i].x = cur->mvec.x;
blob->list[i].y = cur->mvec.y;
blob->list[i].z = cur->mvec.z;
mlist = mlist->next;
free((voidstar)cur);
}
/*
* Allocate room for Intersection Structures and
* Allocate room for an array of pointers to point to
* the Intersection Structures.
*/
blob->ilist = (MetaInt *)RayMalloc( 2 * blob->num * sizeof(MetaInt));
blob->iarr = (MetaInt **)RayMalloc( 2 * blob->num * sizeof(MetaInt *));
return blob;
}
Methods *
BlobMethods()
{
if (iBlobMethods == (Methods *)NULL) {
iBlobMethods = MethodsCreate();
iBlobMethods->create = (GeomCreateFunc *)BlobCreate;
iBlobMethods->methods = BlobMethods;
iBlobMethods->name = BlobName;
iBlobMethods->intersect = BlobIntersect;
iBlobMethods->normal = BlobNormal;
iBlobMethods->bounds = BlobBounds;
iBlobMethods->stats = BlobStats;
iBlobMethods->checkbounds = TRUE;
iBlobMethods->closed = TRUE;
}
return iBlobMethods;
}
/*****************************************************************************
* Function used by qsort() when sorting the Ray/Blob intersection list
*/
int
MetaCompare(A,B)
char *A,*B;
{
MetaInt **AA,**BB;
AA = (MetaInt **) A;
BB = (MetaInt **) B;
if (AA[0]->bound == BB[0]->bound) return(0);
if (AA[0]->bound < BB[0]->bound) return(-1);
return(1); /* AA[0]->bound is > BB[0]->bound */
}
/*****************************************************************************
* Ray/metaball intersection test.
*/
int
BlobIntersect(blob, ray, mindist, maxdist)
Blob *blob;
Ray *ray;
Float mindist, *maxdist;
{
double c[5], s[4];
Float dist;
MetaInt *ilist,**iarr;
register int i,j,inum;
extern void qsort();
BlobTests++;
ilist = blob->ilist;
iarr = blob->iarr;
/*
* The first step in calculating the Ray/Blob intersection is to
* divide the Ray into intervals such that only a fixed set of
* Metaballs contribute to the density function along that interval.
* This is done by finding the set of intersections between the Ray
* and each Metaball's Sphere/Region of influence, which has a
* radius ri and is centered at (xi,yi,zi).
* Intersection information is kept track of in the MetaInt
* structure and consists of:
*
* type indicates whether this intersection is the start(R_START)
* of a Region or the end(R_END) of one.
* pnt the Metaball of this intersection
* bound the distance from Ray origin to this intersection
*
* This list is then sorted by bound and used later to find the Ray/Blob
* intersection.
*/
inum = 0;
for(i=0; i < blob->num; i++)
{
register Float xadj, yadj, zadj;
register Float b, t, rs;
register Float dmin,dmax;
rs = blob->list[i].rs;
xadj = blob->list[i].x - ray->pos.x;
yadj = blob->list[i].y - ray->pos.y;
zadj = blob->list[i].z - ray->pos.z;
/*
* Ray/Sphere of Influence intersection
*/
b = xadj * ray->dir.x + yadj * ray->dir.y + zadj * ray->dir.z;
t = b * b - xadj * xadj - yadj * yadj - zadj * zadj + rs;
/*
* don't except imaginary or single roots. A single root is a ray
* tangent to the Metaball's Sphere/Region. The Metaball's
* contribution to the overall density function at this point is
* zero anyway.
*/
if (t > 0.0) /* we have two roots */
{
t = sqrt(t);
dmin = b - t;
/*
* only interested in stuff in front of ray origin
*/
if (dmin < mindist) dmin = mindist;
dmax = b + t;
if (dmax > dmin) /* we have a valid Region */
{
/*
* Initialize min/start and max/end Intersections Structures
* for this Metaball
*/
ilist[inum].type = R_START;
ilist[inum].pnt = i;
ilist[inum].bound = dmin;
for(j=0;j<5;j++) ilist[inum].c[j] = 0.0;
iarr[inum] = &(ilist[inum]);
inum++;
ilist[inum].type = R_END;
ilist[inum].pnt = i;
ilist[inum].bound = dmax;
for(j=0;j<5;j++) ilist[inum].c[j] = 0.0;
iarr[inum] = &(ilist[inum]);
inum++;
} /* End of valid Region */
} /* End of two roots */
} /* End of loop through metaballs */
/*
* If there are no Ray/Metaball intersections there will
* not be a Ray/Blob intersection. Exit now.
*/
if (inum == 0)
{
return FALSE;
}
/*
* Sort Intersection list. No sense using qsort if there's only
* two intersections.
*
* Note: we actually aren't sorting the Intersection structures, but
* an array of pointers to the Intersection structures.
* This is faster than sorting the Intersection structures themselves.
*/
if (inum==2)
{
MetaInt *t;
if (ilist[0].bound > ilist[1].bound)
{
t = iarr[0];
iarr[0] = iarr[1];
iarr[1] = t;
}
}
else qsort((voidstar)(iarr), (unsigned)inum, sizeof(MetaInt *),
MetaCompare);
/*
* Finding the Ray/Blob Intersection
*
* The non-zero part of the density function for each Metaball is
*
* di(Ri) = c4i * Ri^4 + c2i * Ri^2 + c0i
*
* To find find the Ray/Blob intersection for one metaball
* substitute for distance
*
* Ri^2 = (x-xi)^2 + (y-yi)^2 + (z-zi)^2
*
* and then substitute the Ray equations:
*
* x = x0 + x1 * t
* y = y0 + y1 * t
* z = z0 + z1 * t
*
* to get a big mess :^). Actually, it's a Quartic in t and it's fully
* listed farther down. Here's a short version:
*
* c[4] * t^4 + c[3] * t^3 + c[2] * t^2 + c[1] * t + c[0] = T
*
* Don't forget that the Blob is defined by the density being equal to
* the threshold T.
* To calculate the intersection of a Ray and two or more Metaballs,
* the coefficients are calculated for each Metaball and then added
* together. We can do this since we're working with polynomials.
* The points of intersection are the roots of the resultant equation.
*
* The algorithm loops through the intersection list, calculating
* the coefficients if an intersection is the start of a Region and
* adding them to all intersections in that region.
* When it detects a valid interval, it calculates the
* roots from the starting intersection's coefficients and if any of
* the roots are in the interval, the smallest one is returned.
*
*/
{
register Float *tmpc;
MetaInt *strt,*tmp;
register int istrt,itmp;
register int num,exitflag,inside;
/*
* Start the algorithm outside the first interval
*/
inside = 0;
istrt = 0;
strt = iarr[istrt];
if (strt->type != R_START)
RLerror(RL_WARN,"MetaInt sanity check FAILED!\n");
/*
* Loop through intersection. If a root is found the code
* will return at that point.
*/
while( istrt < inum )
{
/*
* Check for multiple intersections at the same point.
* This is also where the coefficients are calculated
* and spread throughout that Metaball's sphere of
* influence.
*/
do
{
/* find out which metaball */
i = strt->pnt;
/* only at starting boundaries do this */
if (strt->type == R_START)
{
register MetaVector *ml;
register Float a1,a0;
register Float xd,yd,zd;
register Float c4,c2,c0;
/* we're inside */
inside++;
/* As promised, the full equations
*
* c[4] = c4*a2*a2;
* c[3] = 4.0*c4*a1*a2;
* c[2] = 4.0*c4*a1*a1 + 2.0*c4*a2*a0 + c2*a2;
* c[1] = 4.0*c4*a1*a0 + 2.0*c2*a1;
* c[0] = c4*a0*a0 + c2*a0 + c0;
*
* where
* a2 = (x1*x1 + y1*y1 + z1*z1) = 1.0 because the ray
* is normalized
* a1 = (xd*x1 + yd*y1 + zd*z1)
* a0 = (xd*xd + yd*yd + zd*zd)
* xd = (x0 - xi)
* yd = (y0 - yi)
* zd = (z0 - zi)
* (xi,yi,zi) is center of Metaball
* (x0,y0,z0) is Ray origin
* (x1,y1,z1) is normalized Ray direction
* c4,c2,c0 are the coefficients for the
* Metaball's density function
*
*/
/*
* Some compilers would recalculate
* this each time its used.
*/
ml = &(blob->list[i]);
xd = ray->pos.x - ml->x;
yd = ray->pos.y - ml->y;
zd = ray->pos.z - ml->z;
a1 = (xd * ray->dir.x + yd * ray->dir.y
+ zd * ray->dir.z);
a0 = (xd * xd + yd * yd + zd * zd);
c0 = ml->c0;
c2 = ml->c2;
c4 = ml->c4;
c[4] = c4;
c[3] = 4.0*c4*a1;
c[2] = 2.0*c4*(2.0*a1*a1 + a0) + c2;
c[1] = 2.0*a1*(2.0*c4*a0 + c2);
c[0] = c4*a0*a0 + c2*a0 + c0;
/*
* Since we've gone through the trouble of calculating the
* coefficients, put them where they'll be used.
* Starting at the current intersection(It's also the start of
* a region), add the coefficients to each intersection until
* we reach the end of this region.
*/
itmp = istrt;
tmp = strt;
while( (tmp->pnt != i) ||
(tmp->type != R_END) )
{
tmpc = tmp->c;
for(j=0;j<5;j++)
*tmpc++ += c[j];
itmp++;
tmp = iarr[itmp];
}
} /* End of start of a Region */
/*
* Since the intersection wasn't the start
* of a region, it must the end of one.
*/
else inside--;
/*
* If we are inside a region(or regions) and the next
* intersection is not at the same place, then we have
* the start of an interval. Set the exitflag.
*/
if ((inside > 0)
&& (strt->bound != iarr[istrt+1]->bound) )
exitflag = 1;
else
/* move to next intersection */
{
istrt++;
strt = iarr[istrt];
exitflag = 0;
}
/*
* Check to see if we're at the end. If so then exit with
* no intersection found.
*/
if (istrt >= inum)
{
return FALSE;
}
} while(!exitflag);
/* End of Search for valid interval */
/*
* Find Roots along this interval
*/
/* get coefficients from Intersection structure */
tmpc = strt->c;
for(j=0;j<5;j++) c[j] = *tmpc++;
/* Don't forget to put in threshold */
c[0] -= blob->T;
/* Use Graphic Gem's Quartic Root Finder */
num = SolveQuartic(c,s);
/*
* If there are roots, search for roots within the interval.
*/
dist = 0.0;
if (num>0)
{
for(j=0;j<num;j++)
{
/*
* Not sure if EPSILON is truly needed, but it might cause
* small cracks between intervals in some cases. In any case
* I don't believe it hurts.
*/
if ((s[j] >= (strt->bound - EPSILON))
&& (s[j] <= (iarr[istrt+1]->bound +
EPSILON) ) )
{
if (dist == 0.0)
/* first valid root */
dist = s[j];
else if (s[j] < dist)
/* else only if smaller */
dist = s[j];
}
}
/*
* Found a valid root
*/
if (dist > mindist && dist < *maxdist)
{
*maxdist = dist;
BlobHits++;
return TRUE;
/* Yeah! Return valid root */
}
}
/*
* Move to next intersection
*/
istrt++;
strt = iarr[istrt];
} /* End of loop through Intersection List */
} /* End of Solving for Ray/Blob Intersection */
/*
* return negative
*/
return FALSE;
}
/***********************************************
* Find the Normal of a Blob at a given point
*
* The normal of a surface at a point (x0,y0,z0) is the gradient of that
* surface at that point. The gradient is the vector
*
* Fx(x0,y0,z0) , Fy(x0,y0,z0) , Fz(x0,y0,z0)
*
* where Fx(),Fy(),Fz() are the partial dervitives of F() with respect
* to x,y and z respectively. Since the surface of a Blob is made up
* of the sum of one or more polynomials, the normal of a Blob at a point
* is the sum of the gradients of the individual polynomials at that point.
* The individual polynomials in this case are di(Ri) i = 0,...,num .
*
* To find the gradient of the contributing polynomials
* take di(Ri) and substitute U = Ri^2;
*
* di(U) = c4i * U^2 + c2i * U + c0i
*
* dix(U) = 2 * c4i * Ux * U + c2i * Ux
*
* U = (x-xi)^2 + (y-yi)^2 + (z-zi)^2
*
* Ux = 2 * (x-xi)
*
* Rearranging we get
*
* dix(x0,y0,z0) = 4 * c4i * xdi * disti + 2 * c2i * xdi
* diy(x0,y0,z0) = 4 * c4i * ydi * disti + 2 * c2i * ydi
* diz(x0,y0,z0) = 4 * c4i * zdi * disti + 2 * c2i * zdi
*
* where
* xdi = x0-xi
* ydi = y0-yi
* zdi = y0-yi
* disti = xdi*xdi + ydi*ydi + zdi*zdi
* (xi,yi,zi) is the center of Metaball i
* c4i,c2i,c0i are the coefficients of Metaball i's density function
*/
int
BlobNormal(blob, pos, nrm, gnrm)
Blob *blob;
Vector *pos, *nrm, *gnrm;
{
register int i;
/*
* Initialize normals to zero
*/
nrm->x = nrm->y = nrm->z = 0.0;
/*
* Loop through Metaballs. If the point is within a Metaball's
* Sphere of influence, calculate the gradient and add it to the
* normals
*/
for(i=0;i < blob->num; i++)
{
register MetaVector *sl;
register Float dist,xd,yd,zd;
sl = &(blob->list[i]);
xd = pos->x - sl->x;
yd = pos->y - sl->y;
zd = pos->z - sl->z;
dist = xd*xd + yd*yd + zd*zd;
if (dist <= sl->rs )
{
register Float temp;
/* temp is negative so normal points out of blob */
temp = -2.0 * (2.0 * sl->c4 * dist + sl->c2);
nrm->x += xd * temp;
nrm->y += yd * temp;
nrm->z += zd * temp;
}
}
(void)VecNormalize(nrm);
*gnrm = *nrm;
return FALSE;
}
/*****************************************************************************
* Calculate the extent of the Blob
*/
void
BlobBounds(blob, bounds)
Blob *blob;
Float bounds[2][3];
{
Float r;
Float minx,miny,minz,maxx,maxy,maxz;
int i;
/*
* Loop through Metaballs to find the minimun and maximum in each
* direction.
*/
for( i=0; i< blob->num; i++)
{
register Float d;
r = sqrt(blob->list[i].rs);
if (i==0)
{
minx = blob->list[i].x - r;
miny = blob->list[i].y - r;
minz = blob->list[i].z - r;
maxx = blob->list[i].x + r;
maxy = blob->list[i].y + r;
maxz = blob->list[i].z + r;
}
else
{
d = blob->list[i].x - r;
if (d<minx) minx = d;
d = blob->list[i].y - r;
if (d<miny) miny = d;
d = blob->list[i].z - r;
if (d<minz) minz = d;
d = blob->list[i].x + r;
if (d>maxx) maxx = d;
d = blob->list[i].y + r;
if (d>maxy) maxy = d;
d = blob->list[i].z + r;
if (d>maxz) maxz = d;
}
}
bounds[LOW][X] = minx;
bounds[HIGH][X] = maxx;
bounds[LOW][Y] = miny;
bounds[HIGH][Y] = maxy;
bounds[LOW][Z] = minz;
bounds[HIGH][Z] = maxz;
}
char *
BlobName()
{
return blobName;
}
void
BlobStats(tests, hits)
unsigned long *tests, *hits;
{
*tests = BlobTests;
*hits = BlobHits;
}
void
BlobMethodRegister(meth)
UserMethodType meth;
{
if (iBlobMethods)
iBlobMethods->user = meth;
}